/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: NodeSortRecord.java,v 1.5 2005/09/28 13:48:36 pvedula Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.dom;

import java.text.CollationKey;
import java.text.Collator;
import java.util.Locale;

import com.sun.org.apache.xalan.internal.xsltc.CollatorFactory;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.utils.StringComparable;
import com.sun.org.apache.xalan.internal.utils.ObjectFactory;
import com.sun.org.apache.xalan.internal.utils.SecuritySupport;

/**
 * Base class for sort records containing application specific sort keys
 */
public abstract class NodeSortRecord {

  public static final int COMPARE_STRING = 0;
  public static final int COMPARE_NUMERIC = 1;

  public static final int COMPARE_ASCENDING = 0;
  public static final int COMPARE_DESCENDING = 1;

  /**
   * A reference to a collator. May be updated by subclass if the stylesheet
   * specifies a different language (will be updated iff _locale is updated).
   *
   * @deprecated This field continues to exist for binary compatibility. New code should not refer
   * to it.
   */
  private static final Collator DEFAULT_COLLATOR = Collator.getInstance();

  /**
   * A reference to the first Collator
   *
   * @deprecated This field continues to exist for binary compatibility. New code should not refer
   * to it.
   */
  protected Collator _collator = DEFAULT_COLLATOR;
  protected Collator[] _collators;

  /**
   * A locale field that might be set by an instance of a subclass.
   *
   * @deprecated This field continues to exist for binary compatibility. New code should not refer
   * to it.
   */
  protected Locale _locale;

  protected CollatorFactory _collatorFactory;

  protected SortSettings _settings;

  private DOM _dom = null;
  private int _node;           // The position in the current iterator
  private int _last = 0;       // Number of nodes in the current iterator
  private int _scanned = 0;    // Number of key levels extracted from DOM

  private Object[] _values; // Contains Comparable  objects

  /**
   * This constructor is run by a call to ClassLoader in the
   * makeNodeSortRecord method in the NodeSortRecordFactory class. Since we
   * cannot pass any parameters to the constructor in that case we just set
   * the default values here and wait for new values through initialize().
   */
  public NodeSortRecord(int node) {
    _node = node;
  }

  public NodeSortRecord() {
    this(0);
  }

  /**
   * This method allows the caller to set the values that could not be passed
   * to the default constructor.
   */
  public final void initialize(int node, int last, DOM dom,
      SortSettings settings)
      throws TransletException {
    _dom = dom;
    _node = node;
    _last = last;
    _settings = settings;

    int levels = settings.getSortOrders().length;
    _values = new Object[levels];

    String colFactClassname = null;
    try {
      // -- W. Eliot Kimber (eliot@isogen.com)
      colFactClassname =
          SecuritySupport
              .getSystemProperty("com.sun.org.apache.xalan.internal.xsltc.COLLATOR_FACTORY");
    } catch (SecurityException e) {
      // If we can't read the propery, just use default collator
    }

    if (colFactClassname != null) {
      try {
        Object candObj = ObjectFactory.findProviderClass(colFactClassname, true);
        _collatorFactory = (CollatorFactory) candObj;
      } catch (ClassNotFoundException e) {
        throw new TransletException(e);
      }
      Locale[] locales = settings.getLocales();
      _collators = new Collator[levels];
      for (int i = 0; i < levels; i++) {
        _collators[i] = _collatorFactory.getCollator(locales[i]);
      }
      _collator = _collators[0];
    } else {
      _collators = settings.getCollators();
      _collator = _collators[0];
    }
  }

  /**
   * Returns the node for this sort object
   */
  public final int getNode() {
    return _node;
  }

  /**
   *
   */
  public final int compareDocOrder(NodeSortRecord other) {
    return _node - other._node;
  }

  /**
   * Get the string or numeric value of a specific level key for this sort
   * element. The value is extracted from the DOM if it is not already in
   * our sort key vector.
   */
  private final Comparable stringValue(int level) {
    // Get value from our array if possible
    if (_scanned <= level) {
      AbstractTranslet translet = _settings.getTranslet();
      Locale[] locales = _settings.getLocales();
      String[] caseOrder = _settings.getCaseOrders();

      // Get value from DOM if accessed for the first time
      final String str = extractValueFromDOM(_dom, _node, level,
          translet, _last);
      final Comparable key =
          StringComparable.getComparator(str, locales[level],
              _collators[level],
              caseOrder[level]);
      _values[_scanned++] = key;
      return (key);
    }
    return ((Comparable) _values[level]);
  }

  private final Double numericValue(int level) {
    // Get value from our vector if possible
    if (_scanned <= level) {
      AbstractTranslet translet = _settings.getTranslet();

      // Get value from DOM if accessed for the first time
      final String str = extractValueFromDOM(_dom, _node, level,
          translet, _last);
      Double num;
      try {
        num = new Double(str);
      }
      // Treat number as NaN if it cannot be parsed as a double
      catch (NumberFormatException e) {
        num = new Double(Double.NEGATIVE_INFINITY);
      }
      _values[_scanned++] = num;
      return (num);
    }
    return ((Double) _values[level]);
  }

  /**
   * Compare this sort element to another. The first level is checked first,
   * and we proceed to the next level only if the first level keys are
   * identical (and so the key values may not even be extracted from the DOM)
   *
   * !!!!MUST OPTIMISE - THIS IS REALLY, REALLY SLOW!!!!
   */
  public int compareTo(NodeSortRecord other) {
    int cmp, level;
    int[] sortOrder = _settings.getSortOrders();
    int levels = _settings.getSortOrders().length;
    int[] compareTypes = _settings.getTypes();

    for (level = 0; level < levels; level++) {
      // Compare the two nodes either as numeric or text values
      if (compareTypes[level] == COMPARE_NUMERIC) {
        final Double our = numericValue(level);
        final Double their = other.numericValue(level);
        cmp = our.compareTo(their);
      } else {
        final Comparable our = stringValue(level);
        final Comparable their = other.stringValue(level);
        cmp = our.compareTo(their);
      }

      // Return inverse compare value if inverse sort order
      if (cmp != 0) {
        return sortOrder[level] == COMPARE_DESCENDING ? 0 - cmp : cmp;
      }
    }
    // Compare based on document order if all sort keys are equal
    return (_node - other._node);
  }

  /**
   * Returns the array of Collators used for text comparisons in this object.
   * May be overridden by inheriting classes
   */
  public Collator[] getCollator() {
    return _collators;
  }

  /**
   * Extract the sort value for a level of this key.
   */
  public abstract String extractValueFromDOM(DOM dom, int current, int level,
      AbstractTranslet translet,
      int last);

}
